/*************************************************************************
 *                                                                       *
 * This file is part of the FreeMAPs distribution.  For full copyright   *
 * information, see COPYRIGHT and COPYING.LESSER.                        *
 *                                                                       *
 * Author:           sqbang                                              *
 * Email:            sqbang@ustc.edu                                     *
 * Date:             2013.12.03                                          *
 *                                                                       *
 *************************************************************************/

//declaration header
#include "COMMON/tbox/InputDatabase.h"

//COMMON headers
#include "COMMON/info/InfoManager.h"
#include "COMMON/info/CommonInfo.h"
#include "COMMON/tbox/LogMessage.h"

namespace FreeMAPs
{

InputDatabase::InputDatabase (
    const boost::shared_ptr<Database> db):
    d_database  (db),
    d_is_top_db (true)
{
    FREEMAPS_ASSERT (db);
    d_nested_name = db->get_name();
}



InputDatabase::~InputDatabase()
{
    d_key_line_number.clear();
    Utilities::clear_pointer_object (d_sub_databases);
}



void
InputDatabase::set_key_line_number (
    const std::string& key,
    const int line_number)
{
    //FREEMAPS_ASSERT_MSG (this->key_exists (key), "key = \"" << key << "\"");
    if (d_key_line_number.count (key) > 0)
    {
        LogMessage::unknown_error (
            this,
            key,
            "the key is redefined");
    }
    d_key_line_number.insert (std::make_pair (key, line_number));
}



bool
InputDatabase::key_exists (
    const std::string& key) const
{
    FREEMAPS_ASSERT (d_database);
    return (d_database->key_exists (key) || d_map_database.key_exists (key));
}



std::vector<std::string>
InputDatabase::get_keys() const
{
    FREEMAPS_ASSERT (d_database);
    return Utilities::trans_set_to_vec (Utilities::get_combined_set (
                                            Utilities::trans_vec_to_set (d_database->get_all_keys()),
                                            d_map_database.get_keys()));
}



int
InputDatabase::get_array_size (
    const std::string& key) const
{
    FREEMAPS_ASSERT (d_database);
    return d_database->get_array_size (key);
}



bool
InputDatabase::use_default_value (
    const std::string& key) const
{
    if (this->key_exists (key))
    {
        if (this->is_string (key))
        {
            const std::vector<std::string>& value = this->get_string_vector (key);
            if (value.size() == 1 && value[0] == "FREEMAPS_DEFAULT_VALUE")
            {
                return true;
            }
        }
    }

    return false;
}



DataType
InputDatabase::get_key_type (
    const std::string& key) const
{
    if (!this->key_exists (key))
    {
        return DataType::INVALID_VALUE;
    }
    else
    {
        if (this->is_bool (key))
        {
            return DataType::BOOL;
        }
        else if (this->is_char (key))
        {
            return DataType::CHAR;
        }
        else if (this->is_integer (key))
        {
            return DataType::INTEGER;
        }
        else if (this->is_float (key))
        {
            return DataType::FLOAT;
        }
        else if (this->is_double (key))
        {
            return DataType::DOUBLE;
        }
        else if (this->is_complex (key))
        {
            return DataType::COMPLEX;
        }
        else if (this->is_string (key))
        {
            return DataType::STRING;
        }
        else if (this->is_database (key))
        {
            return DataType::DATABASE;
        }
        else if (this->is_database_box (key))
        {
            return DataType::BOX;
        }
        else if (d_map_database.key_exists (key))
        {
            return d_map_database.get_key_type (key);
        }
        else
        {
            return DataType::UNKNOWN;
        }
    }
}



void
InputDatabase::insert_allowable_key (
    const std::string& allowable_key) const
{
    d_allowable_keys.insert (allowable_key);
}



void
InputDatabase::insert_allowable_keys (
    const std::set<std::string>& allowable_keys) const
{
    Utilities::insert (allowable_keys, d_allowable_keys);
}



void
InputDatabase::insert_allowable_key_with_items (
    const std::string& allowable_key) const
{
    d_allowable_keys.insert (allowable_key);

    this->check_optional_key (allowable_key, DataType::STRING, false, true);
    this->check_duplicated_string_value (allowable_key);
    if (this->key_exists (allowable_key))
    {
        Utilities::insert (this->get_string_vector (allowable_key), d_allowable_keys);
    }
}



void
InputDatabase::check_incompatibility_keys (
    const std::set<std::string>& key_group1,
    const std::set<std::string>& key_group2) const
{
    for (std::set<std::string>::const_iterator p1 = key_group1.begin();
            p1 != key_group1.end(); ++p1)
    {
        for (std::set<std::string>::const_iterator p2 = key_group2.begin();
                p2 != key_group2.end(); ++p2)
        {
            this->check_incompatibility_keys (*p1, *p2);
        }
    }
}



void
InputDatabase::check_incompatibility_keys (
    const std::string& key_1,
    const std::string& key_2) const
{
    if (this->key_exists (key_1) && this->key_exists (key_2))
    {
        LogMessage::disallow_mutually_exclusive (
            this,
            key_1,
            key_2);
    }
}



void
InputDatabase::check_either_or_keys (
    const std::string& key_1,
    const std::string& key_2) const
{
    this->check_incompatibility_keys (key_1, key_2);

    if (!this->key_exists (key_1) && !this->key_exists (key_2))
    {
        LogMessage::unknown_error (
            this,
            "\"" + key_1 + "\" and \"" + key_2 + "\" can not miss neither");
    }
}



void
InputDatabase::check_allowable_keys() const
{
    const std::vector<std::string>& keys = this->get_keys();

    /*
     * first check all thos type is string array
     * for example, if a command has type:
     *     key = item1, item2
     *     item1 = ...
     *     item2 = ...
     * then check key first to avoid some error message
     * that is hard to understand.
     */
    for (std::size_t i = 0; i < keys.size(); ++i)
    {
        const std::string& key = keys[i];
        if (this->is_string (key) && this->get_array_size (key) > 1)
        {
            this->check_allowable_key (key);
        }
    }

    /*
     * then check string
     */
    for (std::size_t i = 0; i < keys.size(); ++i)
    {
        const std::string& key = keys[i];
        if (this->is_string (key))
        {
            this->check_allowable_key (key);
        }
    }

    /*
     * then check left
     */
    for (std::size_t i = 0; i < keys.size(); ++i)
    {
        this->check_allowable_key (keys[i]);
    }
}



void
InputDatabase::check_allowable_key (
    const std::string& key) const
{
    if (d_allowable_keys.count (key) == 0)
    {
        /*
         * try to find the most similar key to give usr a tip
         */
        unsigned int similar_key_len = 0;
        std::string similar_key;
        for (std::set<std::string>::const_iterator pos = d_allowable_keys.begin();
                pos != d_allowable_keys.end(); ++pos)
        {
            const unsigned int similar_to_current_key = Utilities::get_num_of_same_chars (key, *pos);
            if (similar_to_current_key > similar_key_len)
            {
                similar_key_len = similar_to_current_key;
                similar_key = *pos;
            }
        }
        if (similar_key_len > 1)
        {
            LogMessage::unrecognized_key (
                this,
                key,
                similar_key,
                d_allowable_keys);
        }
        else
        {
            LogMessage::unrecognized_key (
                this,
                key,
                d_allowable_keys);
        }
    }
}



void
InputDatabase::check_disallowable_key (
    const std::string& disallowable_key,
    const std::string& reason) const
{
    if (this->key_exists (disallowable_key))
    {
        std::ostringstream oss;
        oss << "key of \"" << disallowable_key << "\" is disallowable, the reason is:\n";
        oss << reason;
        LogMessage::unknown_error (
            this,
            oss.str());
    }
}



void
InputDatabase::check_disallowable_keys (
    const std::set<std::string>& disallowable_keys,
    const std::string& reason) const
{
    for (std::set<std::string>::const_iterator pos = disallowable_keys.begin();
            pos != disallowable_keys.end(); ++pos)
    {
        const std::string& disallowable_key = *pos;
        if (this->key_exists (disallowable_key))
        {
            std::ostringstream oss;
            oss << "key of \"" << disallowable_key << "\" is disallowable, the reason is:\n";
            oss << reason;
            LogMessage::unknown_error (
                this,
                oss.str());
        }
    }
}



void
InputDatabase::check_optional_key (
    const std::string& key,
    const DataType data_type,
    const bool can_use_default_value,
    const bool is_array) const
{
    if (!this->key_exists (key))
    {
        return;
    }

    /*
     * if key=="FREEMAPS_DEFAULT_VALUE", do some special check.
     * because may be the key is a int, double... type while
     * FREEMAPS_DEFAULT_VALUE is supported
     */
    if (this->is_string (key))
    {
        this->check_blank_string_key (key);
        const std::vector<std::string>& values = this->get_string_vector (key);
        if ( (values.size() == 1) && values[0] == "FREEMAPS_DEFAULT_VALUE")
        {
            if (can_use_default_value)
            {
                return;
            }
            else
            {
                LogMessage::disallow_using_default_value (
                    this,
                    key);
            }
        }
    }

    if (this->get_array_size (key) > 1 && !is_array)
    {
        LogMessage::disallow_using_array (
            this,
            key);
    }

    /*
     * if is not FREEMAPS_DEFAULT_VALUE, check directly.
     * now compare data_type with Utilities::get_key_type()
     * because if data_type is DOUBLE, then int and float will
     * take as logical.
     */
    switch (data_type)
    {
    case DataType::DATABASE:
        {
            if (!this->is_database (key))
            {
                LogMessage::disallow_type (
                    this,
                    key,
                    Utilities::enum_to_string (data_type));
            }
            return;
        }
    case DataType::BOOL:
        {
            if (!this->is_bool (key))
            {
                LogMessage::disallow_type (
                    this,
                    key,
                    Utilities::enum_to_string (data_type));
            }
            return;
        }
    case DataType::BOX:
        {
            if (!this->is_database_box (key))
            {
                LogMessage::disallow_type (
                    this,
                    key,
                    Utilities::enum_to_string (data_type));
            }
            return;
        }
    case DataType::CHAR:
        {
            if (!this->is_char (key))
            {
                LogMessage::disallow_type (
                    this,
                    key,
                    Utilities::enum_to_string (data_type));
            }
            return;
        }
    case DataType::COMPLEX:
        {
            if (!this->is_complex (key))
            {
                LogMessage::disallow_type (
                    this,
                    key,
                    Utilities::enum_to_string (data_type));
            }
            return;
        }
    case DataType::DOUBLE:
        {
            if (!this->is_double (key))
            {
                LogMessage::disallow_type (
                    this,
                    key,
                    Utilities::enum_to_string (data_type));
            }
            return;
        }
    case DataType::FLOAT:
        {
            if (!this->is_float (key))
            {
                LogMessage::disallow_type (
                    this,
                    key,
                    Utilities::enum_to_string (data_type));
            }
            return;
        }
    case DataType::INTEGER:
        {
            if (!this->is_integer (key))
            {
                LogMessage::disallow_type (
                    this,
                    key,
                    Utilities::enum_to_string (data_type));
            }
            return;
        }
    case DataType::STRING:
        {
            if (!this->is_string (key))
            {
                LogMessage::disallow_type (
                    this,
                    key,
                    Utilities::enum_to_string (data_type));
            }
            return;
        }
    case DataType::STRING_STRING_MAP:
        {
            if (!this->is_string_string_map (key))
            {
                LogMessage::disallow_type (
                    this,
                    key,
                    Utilities::enum_to_string (data_type));
            }
            return;
        }
    case DataType::UNKNOWN:
        {
            return;
        }
    default:
        {
            FREEMAPS_ERROR ("unrecognized type");
        }
    }

    if (data_type != this->get_key_type (key))
    {
        LogMessage::disallow_type (
            this,
            key,
            Utilities::enum_to_string (data_type));
    }
}



void
InputDatabase::check_requisite_key (
    const std::string& key,
    const DataType data_type,
    const bool can_use_default_value,
    const bool is_array) const
{
    if (!this->key_exists (key))
    {
        LogMessage::disallow_missing (
            this,
            key);
    }
    else
    {
        this->check_optional_key (key, data_type, can_use_default_value, is_array);
    }
}



void
InputDatabase::check_requisite_database (
    const std::string& key) const
{
    if (this->key_exists (key))
    {
        this->check_requisite_key (key, DataType::DATABASE, false, false);
    }
    else
    {
        this->put_database (key);
    }
}



void
InputDatabase::check_blank_string_key (
    const std::string& key) const
{
    if (this->key_exists (key) && this->is_string (key))
    {
        const std::vector<std::string>& array = this->get_string_vector (key);

        for (std::size_t i = 0; i < array.size(); ++i)
        {
            if (Utilities::is_blank_string (array[i]))
            {
                LogMessage::disallow_using_blank_string (
                    this,
                    key);
            }
        }
    }
}



void
InputDatabase::check_duplicated_string_value (
    const std::string& key) const
{
    if (!this->key_exists (key))
    {
        return;
    }

    this->check_requisite_key (key, DataType::STRING, false, true);
    const std::vector<std::string>& array = this->get_string_vector (key);

    std::map<std::string, int> data;
    std::ostringstream os;
    for (std::size_t i = 0; i < array.size(); ++i)
    {
        if (data.count (array[i]) > 0)
        {
            const int pre_num = data.find (array[i])->second;
            os << "keyword \"" << key << "\" can't have duplicated value of:\n";
            os << key << "[" << pre_num << "] = ";
            os << key << "[" << i << "] = " << array[i] << std::endl;
            LogMessage::unknown_error (
                this,
                key,
                os.str());
        }

        data.insert (std::make_pair (array[i], i));
    }
}



void
InputDatabase::check_duplicated_integer_value (
    const std::string& key) const
{
    if (!this->key_exists (key))
    {
        return;
    }

    this->check_requisite_key (key, DataType::INTEGER, false, true);
    const std::vector<int>& array = this->get_integer_vector (key);

    std::map<int, int> data;
    std::ostringstream os;
    for (std::size_t i = 0; i < array.size(); ++i)
    {
        if (data.count (array[i]) > 0)
        {
            const int pre_num = data.find (array[i])->second;
            os << "keyword \"" << key << "\" can't have duplicated value of:\n";
            os << key << "[" << pre_num << "] = ";
            os << key << "[" << i << "] = " << array[i] << std::endl;
            LogMessage::unknown_error (
                this,
                key,
                os.str());
        }

        data.insert (std::make_pair (array[i], i));
    }
}



void
InputDatabase::check_allowable_string_value (
    const std::string& key,
    const std::string& tip_info,
    const std::set<std::string>& allowable_values) const
{
    if (!this->key_exists (key))
    {
        return;
    }

    FREEMAPS_ASSERT (this->is_string (key));
    const std::vector<std::string>& value_array = this->get_string_vector (key);
    for (std::size_t i = 0; i < value_array.size(); ++i)
    {
        if (allowable_values.count (value_array[i]) == 0)
        {
            LogMessage::use_improper_string_value (
                this,
                key,
                i,
                tip_info,
                allowable_values);
        }
    }
}



void
InputDatabase::check_disallowable_string_value (
    const std::string& key,
    const std::string& disallowable_value,
    const std::string& tip_info) const
{
    if (!this->key_exists (key))
    {
        return;
    }

    FREEMAPS_ASSERT (this->is_string (key));
    const std::vector<std::string>& value_array = this->get_string_vector (key);
    for (std::size_t i = 0; i < value_array.size(); ++i)
    {
        if (value_array[i] == disallowable_value)
        {
            LogMessage::use_improper_string_value (
                this,
                key,
                i,
                tip_info);
        }
    }
}



void
InputDatabase::check_allowable_integer_value (
    const std::string& key,
    const int min,
    const int max) const
{
    if (!this->key_exists (key))
    {
        return;
    }

    FREEMAPS_ASSERT (this->is_integer (key));
    const std::vector<int>& value_array = this->get_integer_vector (key);
    for (std::size_t i = 0; i < value_array.size(); ++i)
    {
        const int value = value_array[i];
        if (value < min || value > max)
        {
            std::ostringstream os;
            os << key;
            if (value_array.size() > 1)
            {
                os << "[" << i << "]";
            }
            os << " = " << value << " is not supported, \n";
            os << "it should take value from " << min << " to " << max << '\n';

            LogMessage::unknown_error (
                this,
                key,
                os.str());
        }
    }
}



void
InputDatabase::check_allowable_min_integer_value (
    const std::string& key,
    const int min) const
{
    if (!this->key_exists (key))
    {
        return;
    }

    FREEMAPS_ASSERT (this->is_integer (key));
    const std::vector<int>& value_array = this->get_integer_vector (key);
    for (std::size_t i = 0; i < value_array.size(); ++i)
    {
        const int value = value_array[i];
        if (value < min)
        {
            std::ostringstream os;
            os << key;
            if (value_array.size() > 1)
            {
                os << "[" << i << "]";
            }
            os << " = " << value << " is not supported, \n";
            os << "it should take value greater than " << min << '\n';

            LogMessage::unknown_error (
                this,
                key,
                os.str());
        }
    }
}



void
InputDatabase::check_allowable_max_integer_value (
    const std::string& key,
    const int max) const
{
    if (!this->key_exists (key))
    {
        return;
    }

    FREEMAPS_ASSERT (this->is_integer (key));
    const std::vector<int>& value_array = this->get_integer_vector (key);
    for (std::size_t i = 0; i < value_array.size(); ++i)
    {
        const int value = value_array[i];
        if (value > max)
        {
            std::ostringstream os;
            os << key;
            if (value_array.size() > 1)
            {
                os << "[" << i << "]";
            }
            os << " = " << value << " is not supported, \n";
            os << "it should take value less than " << max << '\n';

            LogMessage::unknown_error (
                this,
                key,
                os.str());
        }
    }
}



void
InputDatabase::check_allowable_double_value (
    const std::string& key,
    const double min,
    const double max) const
{
    if (!this->key_exists (key))
    {
        return;
    }

    FREEMAPS_ASSERT (this->is_double (key));
    const std::vector<double>& value_array = this->get_double_vector (key);
    for (std::size_t i = 0; i < value_array.size(); ++i)
    {
        const double value = value_array[i];
        if (value < min || value > max)
        {
            std::ostringstream os;
            os << key;
            if (value_array.size() > 1)
            {
                os << "[" << i << "]";
            }
            os << " = " << value << " is not supported, \n";
            os << "it should take value from " << min << " to " << max << '\n';

            LogMessage::unknown_error (
                this,
                key,
                os.str());
        }
    }
}



void
InputDatabase::check_allowable_min_double_value (
    const std::string& key,
    const double min) const
{
    if (!this->key_exists (key))
    {
        return;
    }

    FREEMAPS_ASSERT (this->is_double (key));
    const std::vector<double>& value_array = this->get_double_vector (key);
    for (std::size_t i = 0; i < value_array.size(); ++i)
    {
        const double value = value_array[i];
        if (value < min)
        {
            std::ostringstream os;
            os << key;
            if (value_array.size() > 1)
            {
                os << "[" << i << "]";
            }
            os << " = " << value << " is not supported, \n";
            os << "it should take value greater than " << min << '\n';

            LogMessage::unknown_error (
                this,
                key,
                os.str());
        }
    }
}



void
InputDatabase::check_allowable_max_double_value (
    const std::string& key,
    const double max) const
{
    if (!this->key_exists (key))
    {
        return;
    }

    FREEMAPS_ASSERT (this->is_double (key));
    const std::vector<double>& value_array = this->get_double_vector (key);
    for (std::size_t i = 0; i < value_array.size(); ++i)
    {
        const double value = value_array[i];
        if (value > max)
        {
            std::ostringstream os;
            os << key;
            if (value_array.size() > 1)
            {
                os << "[" << i << "]";
            }
            os << " = " << value << " is not supported, \n";
            os << "it should take value less than " << max << '\n';

            LogMessage::unknown_error (
                this,
                key,
                os.str());
        }
    }
}



void
InputDatabase::check_vec_size_of_dim (
    const std::string& key) const
{
    if (!this->key_exists (key))
    {
        return;
    }

    const int dim = InfoManager::get_instance()->get_common_info()->get_dim();

    if (this->get_array_size (key) != dim)
    {
        LogMessage::disallow_non_dim_size (
            this,
            key);
    }
}



void
InputDatabase::check_vec_size(
    const std::string& key,
    const int allowable_size) const
{
    if (!this->key_exists (key))
    {
        return;
    }

    if (this->get_array_size (key) != allowable_size)
    {
        LogMessage::disallow_array_size(
            this,
            key,
            allowable_size,
            "mismatching vector size");
    }
}



void
InputDatabase::check_same_size (
    const std::string& key_1,
    const std::string& key_2) const
{
    if (!this->key_exists (key_1) || !this->key_exists (key_2))
    {
        return;
    }

    if (this->get_array_size (key_1) != this->get_array_size (key_2))
    {
        LogMessage::disallow_different_size (
            this,
            key_1,
            key_2);
    }
}



bool
InputDatabase::is_database (
    const std::string& key) const
{
    FREEMAPS_ASSERT (d_database);
    return d_database->is_database (key);
}



InputDatabase*
InputDatabase::put_database (
    const std::string& key) const
{
    FREEMAPS_ASSERT (d_database);

    InputDatabase* sub_db (new InputDatabase (d_database->put_database (key)));

    sub_db->d_is_top_db = false;
    if (d_is_top_db)
    {
        sub_db->d_nested_name = key;
    }
    else
    {
        sub_db->d_nested_name = d_nested_name + " --> " + key;
    }

    sub_db->d_parent_db = const_cast<InputDatabase*> (this);
    const_cast<InputDatabase*> (this)->d_sub_databases.insert (std::make_pair (key, sub_db));

    return sub_db;
}



const InputDatabase*
InputDatabase::get_database (
    const std::string& key) const
{
    //std::cout << "when get database, key = " << key << std::endl;
    FREEMAPS_ASSERT (d_sub_databases.count (key) > 0);
    return d_sub_databases.find (key)->second;
}



bool
InputDatabase::is_bool (
    const std::string& key) const
{
    FREEMAPS_ASSERT (d_database);
    return d_database->is_bool (key);
}



void
InputDatabase::put_bool (
    const std::string& key,
    const bool& data) const
{
    FREEMAPS_ASSERT (d_database);
    d_database->put_bool (key, data);
}



void
InputDatabase::put_bool_vector (
    const std::string& key,
    const std::vector<int>& data) const
{
    FREEMAPS_ASSERT (d_database);
    d_database->put_bool_vector (key, data);
}



void
InputDatabase::put_bool_array (
    const std::string& key,
    const bool* const data,
    const int nelements) const
{
    FREEMAPS_ASSERT (d_database);
    d_database->put_bool_array (key, data, nelements);
}



bool
InputDatabase::get_bool (
    const std::string& key) const
{
    this->check_requisite_key (key, DataType::BOOL, false, false);
    const std::vector<bool>& value = this->get_bool_vector (key);
    if (value.size() != 1)
    {
        LogMessage::disallow_using_array (
            this,
            key);
    }

    return value[0];
}



bool
InputDatabase::get_bool_with_default (
    const std::string& key,
    const bool& default_value) const
{
    if (this->key_exists (key))
    {
        if (this->is_string (key))
        {
            const std::vector<std::string>& value = this->get_string_vector (key);
            if (value.size() == 1 && value[0] == "FREEMAPS_DEFAULT_VALUE")
            {
                return default_value;
            }
            else
            {
                LogMessage::disallow_type (
                    this,
                    key,
                    "BOOL");
            }
        }
        else
        {
            FREEMAPS_ASSERT (this->is_bool (key));
            return this->get_bool (key);
        }
    }

    return default_value;
}



std::vector<bool>
InputDatabase::get_bool_vector (
    const std::string& key) const
{
    FREEMAPS_ASSERT (d_database && d_database->is_bool (key));
    return d_database->get_bool_vector (key);
}



void
InputDatabase::get_bool_array (
    const std::string& key,
    bool* data,
    const int nelements) const
{
    FREEMAPS_ASSERT (d_database && d_database->is_bool (key));
    d_database->get_bool_array (key, data, nelements);
}



void
InputDatabase::get_bool_vector_with_default (
    const std::string& key,
    const bool default_value,
    const int default_size,
    std::vector<bool>& result_array) const
{
    if (!this->key_exists (key))
    {
        FREEMAPS_ASSERT (default_size >= 0);
        result_array.resize (default_size, default_value);
    }
    else
    {
        if (this->is_string (key))
        {
            const std::vector<std::string>& value = this->get_string_vector (key);
            if (value.size() == 1 && value[0] == "FREEMAPS_DEFAULT_VALUE")
            {
                FREEMAPS_ASSERT (default_size >= 0);
                result_array.resize (default_size, default_value);
                return;
            }
            else
            {
                LogMessage::disallow_type (
                    this,
                    key,
                    "BOOL");
            }
        }
        else
        {
            FREEMAPS_ASSERT (this->is_bool (key));
            result_array = this->get_bool_vector (key);
        }
    }
}



bool
InputDatabase::is_database_box (
    const std::string& key) const
{
    FREEMAPS_ASSERT (d_database);
    return d_database->is_database_box (key);
}



void
InputDatabase::put_database_box (
    const std::string& key,
    const DatabaseBox& data) const
{
    FREEMAPS_ASSERT (d_database);
    d_database->put_database_box (key, data);
}



void
InputDatabase::put_database_box_vector (
    const std::string& key,
    const std::vector<DatabaseBox>& data) const
{
    FREEMAPS_ASSERT (d_database);
    d_database->put_database_box_vector (key, data);
}



void
InputDatabase::put_database_box_array (
    const std::string& key,
    const DatabaseBox* const data,
    const int nelements) const
{
    FREEMAPS_ASSERT (d_database);
    d_database->put_database_box_array (key, data, nelements);
}



DatabaseBox
InputDatabase::get_database_box (
    const std::string& key) const
{
    FREEMAPS_ASSERT (d_database && d_database->is_database_box (key));
    return d_database->get_database_box (key);
}



DatabaseBox
InputDatabase::get_database_box_with_default (
    const std::string& key,
    const DatabaseBox& defaultvalue) const
{
    FREEMAPS_ASSERT (d_database);
    return d_database->get_database_box_with_default (key, defaultvalue);
}



std::vector<DatabaseBox>
InputDatabase::get_database_box_vector (
    const std::string& key) const
{
    FREEMAPS_ASSERT (d_database);
    return d_database->get_database_box_vector (key);
}



void
InputDatabase::get_database_box_array (
    const std::string& key,
    DatabaseBox* data,
    const int nelements) const
{
    FREEMAPS_ASSERT (d_database);
    d_database->get_database_box_array (key, data, nelements);
}



bool
InputDatabase::is_char (
    const std::string& key) const
{
    FREEMAPS_ASSERT (d_database);
    return d_database->is_char (key);
}



void
InputDatabase::put_char (
    const std::string& key,
    const char& data) const
{
    FREEMAPS_ASSERT (d_database);
    d_database->put_char (key, data);
}



void
InputDatabase::put_char_vector (
    const std::string& key,
    const std::vector<char>& data) const
{
    FREEMAPS_ASSERT (d_database);
    d_database->put_char_vector (key, data);
}



void
InputDatabase::put_char_array (
    const std::string& key,
    const char* const data,
    const int nelements) const
{
    FREEMAPS_ASSERT (d_database);
    d_database->put_char_array (key, data, nelements);
}



char
InputDatabase::get_char (
    const std::string& key) const
{
    this->check_requisite_key (key, DataType::CHAR, false, false);
    const std::vector<char>& value = this->get_char_vector (key);
    if (value.size() != 1)
    {
        LogMessage::disallow_using_array (
            this,
            key);
    }

    return value[0];
}



char
InputDatabase::get_char_with_default (
    const std::string& key,
    const char& default_value) const
{
    if (this->key_exists (key))
    {
        if (this->is_string (key))
        {
            const std::vector<std::string>& value = this->get_string_vector (key);
            if (value.size() == 1 && value[0] == "FREEMAPS_DEFAULT_VALUE")
            {
                return default_value;
            }
            else
            {
                LogMessage::disallow_type (
                    this,
                    key,
                    "CHAR");
            }
        }
        else
        {
            FREEMAPS_ASSERT (this->is_char (key));
            return this->get_char (key);
        }
    }

    return default_value;
}



std::vector<char>
InputDatabase::get_char_vector (
    const std::string& key) const
{
    FREEMAPS_ASSERT (d_database && d_database->is_char (key));
    return d_database->get_char_vector (key);
}



void
InputDatabase::get_char_array (
    const std::string& key,
    char* data,
    const int nelements) const
{
    FREEMAPS_ASSERT (d_database);
    d_database->get_char_array (key, data, nelements);
}



void
InputDatabase::get_char_vector_with_default (
    const std::string& key,
    const char default_value,
    const int default_size,
    std::vector<char>& result_array) const
{
    if (!this->key_exists (key))
    {
        FREEMAPS_ASSERT (default_size >= 0);
        result_array.resize (default_size, default_value);
    }
    else
    {
        if (this->is_string (key))
        {
            const std::vector<std::string>& value = this->get_string_vector (key);
            if (value.size() == 1 && value[0] == "FREEMAPS_DEFAULT_VALUE")
            {
                FREEMAPS_ASSERT (default_size >= 0);
                result_array.resize (default_size, default_value);
                return;
            }
            else
            {
                LogMessage::disallow_type (
                    this,
                    key,
                    "CHAR");
            }
        }
        else
        {
            FREEMAPS_ASSERT (this->is_char (key));
            result_array = this->get_char_vector (key);
        }
    }
}



bool
InputDatabase::is_complex (
    const std::string& key) const
{
    FREEMAPS_ASSERT (d_database);
    return d_database->is_complex (key);
}



void
InputDatabase::put_complex (
    const std::string& key,
    const Complex& data) const
{
    FREEMAPS_ASSERT (d_database);
    d_database->put_complex (key, data);
}



void
InputDatabase::put_complex_vector (
    const std::string& key,
    const std::vector<Complex>& data) const
{
    FREEMAPS_ASSERT (d_database);
    d_database->put_complex_vector (key, data);
}



void
InputDatabase::put_complex_array (
    const std::string& key,
    const Complex* const data,
    const int nelements) const
{
    FREEMAPS_ASSERT (d_database);
    d_database->put_complex_array (key, data, nelements);
}



Complex
InputDatabase::get_complex (
    const std::string& key) const
{
    FREEMAPS_ASSERT (d_database && d_database->is_complex (key));
    return d_database->get_complex (key);
}



Complex
InputDatabase::get_complex_with_default (
    const std::string& key,
    const Complex& defaultvalue) const
{
    FREEMAPS_ASSERT (d_database);
    return d_database->get_complex_with_default (key, defaultvalue);
}



std::vector<Complex>
InputDatabase::get_complex_vector (
    const std::string& key) const
{
    FREEMAPS_ASSERT (d_database && d_database->is_complex (key));
    return d_database->get_complex_vector (key);
}



void
InputDatabase::get_complex_array (
    const std::string& key,
    Complex* data,
    const int nelements) const
{
    FREEMAPS_ASSERT (d_database && d_database->is_complex (key));
    d_database->get_complex_array (key, data, nelements);
}



bool
InputDatabase::is_string_string_map (
    const std::string& key) const
{
    return d_map_database.is_string_string_map (key);
}



void
InputDatabase::put_string_string_map (
    const std::string& key,
    const std::map<std::string, std::string>& data) const
{
    d_map_database.put_string_string_map (key, data);
}



std::map<std::string, std::string>
InputDatabase::get_string_string_map (
    const std::string& key) const
{
    return d_map_database.get_string_string_map (key);
}



std::pair<std::string, std::string>
InputDatabase::get_string_string_pair (
    const std::string& key) const
{
    std::map<std::string, std::string> value = d_map_database.get_string_string_map (key);
    if (value.size() > 1)
    {
        LogMessage::unknown_error (
            this,
            key,
            "it should be a pair instead of a map");
    }

    return *value.begin();
}



bool
InputDatabase::is_double (
    const std::string& key) const
{
    FREEMAPS_ASSERT (d_database);
    return d_database->is_double (key);
}



void
InputDatabase::put_double (
    const std::string& key,
    const double& data) const
{
    FREEMAPS_ASSERT (d_database);
    d_database->put_double (key, data);
}



void
InputDatabase::put_double_vector (
    const std::string& key,
    const std::vector<double>& data) const
{
    FREEMAPS_ASSERT (d_database);
    d_database->put_double_vector (key, data);
}



void
InputDatabase::put_double_array (
    const std::string& key,
    const double* const data,
    const int nelements) const
{
    FREEMAPS_ASSERT (d_database);
    d_database->put_double_array (key, data, nelements);
}



double
InputDatabase::get_double (
    const std::string& key) const
{
    this->check_requisite_key (key, DataType::DOUBLE, false, false);
    const std::vector<double>& value = this->get_double_vector (key);
    if (value.size() != 1)
    {
        LogMessage::disallow_using_array (
            this,
            key);
    }

    return value[0];
}



double
InputDatabase::get_double_with_default (
    const std::string& key,
    const double& default_value) const
{
    if (this->key_exists (key))
    {
        if (this->is_string (key))
        {
            const std::vector<std::string>& value = this->get_string_vector (key);
            if (value.size() == 1 && value[0] == "FREEMAPS_DEFAULT_VALUE")
            {
                return default_value;
            }
            else
            {
                LogMessage::disallow_type (
                    this,
                    key,
                    "DOUBLE");
            }
        }
        else
        {
            FREEMAPS_ASSERT (this->is_double (key));
            return this->get_double (key);
        }
    }

    return default_value;
}



std::vector<double>
InputDatabase::get_double_vector (
    const std::string& key) const
{
    FREEMAPS_ASSERT (d_database && d_database->is_double (key));
    return d_database->get_double_vector (key);
}



void
InputDatabase::get_double_array (
    const std::string& key,
    double* data,
    const int nelements) const
{
    FREEMAPS_ASSERT (d_database);
    d_database->get_double_array (key, data, nelements);
}



void
InputDatabase::get_double_vector_with_default (
    const std::string& key,
    const double default_value,
    const int default_size,
    std::vector<double>& result_array) const
{
    if (!this->key_exists (key))
    {
        FREEMAPS_ASSERT (default_size >= 0);
        result_array.resize (default_size, default_value);
    }
    else
    {
        if (this->is_string (key))
        {
            const std::vector<std::string>& value = this->get_string_vector (key);
            if (value.size() == 1 && value[0] == "FREEMAPS_DEFAULT_VALUE")
            {
                FREEMAPS_ASSERT (default_size >= 0);
                result_array.resize (default_size, default_value);
                return;
            }
            else
            {
                LogMessage::disallow_type (
                    this,
                    key,
                    "DOUBLE");
            }
        }
        else
        {
            FREEMAPS_ASSERT (this->is_double (key));
            result_array = this->get_double_vector (key);
        }
    }
}



bool
InputDatabase::is_float (
    const std::string& key) const
{
    FREEMAPS_ASSERT (d_database);
    return d_database->is_float (key);
}



void
InputDatabase::put_float (
    const std::string& key,
    const float& data) const
{
    FREEMAPS_ASSERT (d_database);
    d_database->put_float (key, data);
}



void
InputDatabase::put_float_vector (
    const std::string& key,
    const std::vector<float>& data) const
{
    FREEMAPS_ASSERT (d_database);
    d_database->put_float_vector (key, data);
}



void
InputDatabase::put_float_array (
    const std::string& key,
    const float* const data,
    const int nelements) const
{
    FREEMAPS_ASSERT (d_database);
    d_database->put_float_array (key, data, nelements);
}



float
InputDatabase::get_float (
    const std::string& key) const
{
    FREEMAPS_ASSERT (d_database && d_database->is_float (key));
    return d_database->get_float (key);
}



float
InputDatabase::get_float_with_default (
    const std::string& key,
    const float& defaultvalue) const
{
    FREEMAPS_ASSERT (d_database);
    return d_database->get_float_with_default (key, defaultvalue);
}



std::vector<float>
InputDatabase::get_float_vector (
    const std::string& key) const
{
    FREEMAPS_ASSERT (d_database && d_database->is_float (key));
    return d_database->get_float_vector (key);
}



void
InputDatabase::get_float_array (
    const std::string& key,
    float* data,
    const int nelements) const
{
    FREEMAPS_ASSERT (d_database && d_database->is_float (key));
    d_database->get_float_array (key, data, nelements);
}



bool
InputDatabase::is_integer (
    const std::string& key) const
{
    FREEMAPS_ASSERT (d_database);
    return d_database->is_integer (key);
}



void
InputDatabase::put_integer (
    const std::string& key,
    const int& data) const
{
    FREEMAPS_ASSERT (d_database);
    d_database->put_integer (key, data);
}



void
InputDatabase::put_integer_vector (
    const std::string& key,
    const std::vector<int>& data) const
{
    FREEMAPS_ASSERT (d_database);
    d_database->put_integer_vector (key, data);
}



void
InputDatabase::put_integer_array (
    const std::string& key,
    const int* const data,
    const int nelements) const
{
    FREEMAPS_ASSERT (d_database);
    d_database->put_integer_array (key, data, nelements);
}



int
InputDatabase::get_integer (
    const std::string& key) const
{
    this->check_requisite_key (key, DataType::INTEGER, false, false);
    const std::vector<int>& value = this->get_integer_vector (key);
    if (value.size() != 1)
    {
        LogMessage::disallow_using_array (
            this,
            key);
    }

    return value[0];
}



int
InputDatabase::get_integer_with_default (
    const std::string& key,
    const int& default_value) const
{
    if (this->key_exists (key))
    {
        if (this->is_string (key))
        {
            const std::vector<std::string>& value = this->get_string_vector (key);
            if (value.size() == 1 && value[0] == "FREEMAPS_DEFAULT_VALUE")
            {
                return default_value;
            }
            else
            {
                LogMessage::disallow_type (
                    this,
                    key,
                    "INTEGER");
            }
        }
        else
        {
            FREEMAPS_ASSERT (this->is_integer (key));
            return this->get_integer (key);
        }
    }

    return default_value;
}



std::vector<int>
InputDatabase::get_integer_vector (
    const std::string& key) const
{
    FREEMAPS_ASSERT (d_database && d_database->is_integer (key));
    return d_database->get_integer_vector (key);
}



void
InputDatabase::get_integer_array (
    const std::string& key,
    int* data,
    const int nelements) const
{
    FREEMAPS_ASSERT (d_database && d_database->is_integer (key));
    d_database->get_integer_array (key, data, nelements);
}



void
InputDatabase::get_integer_vector_with_default (
    const std::string& key,
    const int default_value,
    const int default_size,
    std::vector<int>& result_array) const
{
    if (!this->key_exists (key))
    {
        FREEMAPS_ASSERT (default_size >= 0);
        result_array.resize (default_size, default_value);
    }
    else
    {
        if (this->is_string (key))
        {
            const std::vector<std::string>& value = this->get_string_vector (key);
            if (value.size() == 1 && value[0] == "FREEMAPS_DEFAULT_VALUE")
            {
                FREEMAPS_ASSERT (default_size >= 0);
                result_array.resize (default_size, default_value);
                return;
            }
            else
            {
                LogMessage::disallow_type (
                    this,
                    key,
                    "INTEGER");
            }
        }
        else
        {
            FREEMAPS_ASSERT (this->is_integer (key));
            result_array = this->get_integer_vector (key);
        }
    }
}



bool
InputDatabase::is_string (
    const std::string& key) const
{
    FREEMAPS_ASSERT (d_database);
    return d_database->is_string (key);
}



void
InputDatabase::put_string (
    const std::string& key,
    const std::string& data) const
{
    FREEMAPS_ASSERT (d_database);
    d_database->put_string (key, data);
}



void
InputDatabase::put_string_vector (
    const std::string& key,
    const std::vector<std::string>& data) const
{
    FREEMAPS_ASSERT (d_database);
    d_database->put_string_vector (key, data);
}



void
InputDatabase::put_string_array (
    const std::string& key,
    const std::string* const data,
    const int nelements) const
{
    FREEMAPS_ASSERT (d_database);
    d_database->put_string_array (key, data, nelements);
}



std::string
InputDatabase::get_string (
    const std::string& key) const
{
    this->check_requisite_key (key, DataType::STRING, false, false);
    const std::vector<std::string>& value = this->get_string_vector (key);

    if (value.size() != 1)
    {
        LogMessage::disallow_using_array (
            this,
            key);
    }

    return value[0];
}



std::string
InputDatabase::get_string_with_default (
    const std::string& key,
    const std::string& default_value) const
{
    if (this->key_exists (key))
    {
        FREEMAPS_ASSERT (this->is_string (key));

        const std::vector<std::string>& value = this->get_string_vector (key);
        if (value.size() == 1 && value[0] == "FREEMAPS_DEFAULT_VALUE")
        {
            return default_value;
        }

        return this->get_string (key);
    }

    return default_value;
}



std::vector<std::string>
InputDatabase::get_string_vector (
    const std::string& key) const
{
    FREEMAPS_ASSERT (d_database && d_database->is_string (key));
    return d_database->get_string_vector (key);
}



void
InputDatabase::get_string_array (
    const std::string& key,
    std::string* data,
    const int nelements) const
{
    FREEMAPS_ASSERT (d_database && d_database->is_string (key));
    d_database->get_string_array (key, data, nelements);
}



void
InputDatabase::get_string_vector_with_default (
    const std::string& key,
    const std::string& default_value,
    const int default_size,
    std::vector<std::string>& result_array) const
{
    if (!this->key_exists (key))
    {
        FREEMAPS_ASSERT (default_size >= 0);
        result_array.resize (default_size, default_value);
    }
    else
    {
        FREEMAPS_ASSERT (this->is_string (key));
        result_array = this->get_string_vector (key);
        if (result_array.size() == 1 && result_array[0] == "FREEMAPS_DEFAULT_VALUE")
        {
            FREEMAPS_ASSERT (default_size >= 0);
            result_array.resize (default_size, default_value);
        }
    }
}



std::ostream&
operator << (
    std::ostream& os,
    const InputDatabase& rhs)
{
    rhs.d_database->print_class_data (os);
    return os;
}

const InputDatabase* InputDatabase::get_top_database()const
{
    if(d_is_top_db)return this;
    FREEMAPS_ASSERT_VALID_POINTER(d_parent_db);
    return d_parent_db->get_top_database();
}

std::string
InputDatabase::get_file_name()const
{
    if (d_is_top_db)
    {
        return d_database->get_name();
    }
    else
    {
        return d_parent_db->get_file_name();
    }
}

}
